package com.ct.ecommerce.framework.core;

import com.esotericsoftware.reflectasm.ConstructorAccess;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.util.CollectionUtils;
/**
 * @author changtong
 * @since 2021/5/19
 */
public final class BeanCopierUtils {

    private BeanCopierUtils() {
    }

    private static final Map<String, BeanCopier> BEAN_COPIER_CACHE = new ConcurrentHashMap<>();

    private static final Map<String, ConstructorAccess> CONSTRUCTOR_ACCESS_CACHE = new ConcurrentHashMap<>();

    public static <T> T copyProperties(Object source, Class<T> targetClazz) {
        return copy(source, targetClazz);
    }

    private static <T, E> E copy(T source, Class<E> targetClazz) {

        ConstructorAccess<E> constructorAccess = getConstructorAccess(targetClazz);
        E targetObject = constructorAccess.newInstance();
        BeanCopier copier = getBeanCopier(source.getClass(), targetObject.getClass());
        copier.copy(source, targetObject, null);
        return targetObject;
    }

    private static BeanCopier getBeanCopier(Class sourceClazz, Class targetClazz) {
        String beanKey = generateKey(sourceClazz, targetClazz);
        BeanCopier copier;
        if (!BEAN_COPIER_CACHE.containsKey(beanKey)) {
            copier = BeanCopier.create(sourceClazz, targetClazz, false);
            BEAN_COPIER_CACHE.put(beanKey, copier);
        } else {
            copier = BEAN_COPIER_CACHE.get(beanKey);
        }
        return copier;
    }

    private static String generateKey(Class<?> source, Class<?> target) {
        return new StringJoiner("@")
            .add(source.getName())
            .add(target.getName()).toString();
    }


    public static <T> List<T> copyPropertiesOfList(List<?> sourceList, Class<T> targetClazz) {
        if (CollectionUtils.isEmpty(sourceList)) {
            return Collections.emptyList();
        }

        return sourceList.stream()
            .map(s -> copy(s, targetClazz)).collect(Collectors.toList());

    }

    private static <T> ConstructorAccess<T> getConstructorAccess(Class<T> targetClazz) {
        ConstructorAccess<T> constructorAccess = CONSTRUCTOR_ACCESS_CACHE
            .get(targetClazz.getName());
        if (constructorAccess != null) {
            return constructorAccess;
        }
        try {
            constructorAccess = ConstructorAccess.get(targetClazz);
            constructorAccess.newInstance();
            CONSTRUCTOR_ACCESS_CACHE.put(targetClazz.getName(), constructorAccess);
        } catch (Exception e) {
            throw new RuntimeException(
                String.format("Create new instance of %s failed: %s", targetClazz, e.getMessage()));
        }
        return constructorAccess;
    }

}